﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Player.cs" company="">
//   
// </copyright>
// <summary>
// A participant in a game.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

/*
 * Author: Juan Mompean Esteban, Mikkel Thordal
 * Created: 17-11-2011
 */

namespace ITU_vs_Zombies.Character
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;

    using ITU_vs_Zombies.Game;
    using ITU_vs_Zombies.Graphics;
    using ITU_vs_Zombies.Menu;
    using ITU_vs_Zombies.Movement;
    using ITU_vs_Zombies.Stuff;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;

    /// <summary>
    /// A participant in the game.
    /// </summary>
    [Serializable]
    public class Player : IGraphicsHelper
    {
        #region Constants and Fields

        /// <summary>
        /// The change weapon name.
        /// </summary>
        public const string ChangeWeaponName = @"Sounds\ChangeWeapon";

        /// <summary>
        /// The speed.
        /// </summary>
        public const uint Speed = 5;

        /// <summary>
        /// The static y pos.
        /// </summary>
        public const float StaticYPos = 342f;

        /// <summary>
        /// Gets or sets Image.
        /// </summary>
        public readonly string Image;

        /// <summary>
        /// The change weapon.
        /// </summary>
        [NonSerialized]
        public SoundEffect ChangeWeaponSound;

        /// <summary>
        /// The input state.
        /// </summary>
        [NonSerialized]
        public InputState InputState = new InputState();

        /// <summary>
        /// The texture.
        /// </summary>
        [NonSerialized]
        public Texture2D Texture;

        /// <summary>
        /// The distance from the left/right side of the screen
        /// where the player should not move
        /// </summary>
        private const double NoMovementsScale = 0.3;

        /// <summary>
        /// The number frames.
        /// </summary>
        private readonly uint numberFrames;

        /// <summary>
        /// The active.
        /// </summary>
        private bool active;

        /// <summary>
        /// The is attacking.
        /// </summary>
        private bool isAttacking;

        /// <summary>
        /// The jump speed.
        /// </summary>
        private float jumpSpeed;

        /// <summary>
        /// The jumping.
        /// </summary>
        private bool jumping;

        /// <summary>
        /// The life.
        /// </summary>
        private uint life;

        /// <summary>
        /// The maximum life.
        /// </summary>
        private uint maximumLife;

        /// <summary>
        /// The money.
        /// </summary>
        private uint money;

        /// <summary>
        /// The name.
        /// </summary>
        private string name;

        /// <summary>
        /// The number floor.
        /// </summary>
        private int numberFloor;

        /// <summary>
        /// The position.
        /// </summary>
        private Vector2 position;

        /// <summary>
        /// The score.
        /// </summary>
        private uint score;

        /// <summary>
        /// The start y.
        /// </summary>
        private float startY;

        /// <summary>
        /// The weapons.
        /// </summary>
        private List<Weapon> weapons;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Player"/> class.
        /// </summary>
        /// <param name="life">
        /// The life.
        /// </param>
        /// <param name="image">
        /// The image.
        /// </param>
        /// <param name="numberFrames">
        /// The number Frames.
        /// </param>
        /// <param name="name">
        /// The name.
        /// </param>
        public Player(uint life, string image, uint numberFrames, string name)
        {
            Contract.Requires(life > 0);
            Contract.Ensures(this.Score == 0);
            Contract.Ensures(this.Money == 0);
            this.MaximumLife = life;
            this.Life = life;
            this.Image = image;
            this.Name = name;
            this.Money = 0;
            this.Position = new Vector2(0, StaticYPos);
            this.Score = 0;
            this.Weapons = new List<Weapon>();
            this.Animation = new Animation();
            this.Active = true;
            this.numberFrames = numberFrames;
            this.Direction = MovementsEnum.Right;
            this.numberFloor = 0;
            this.Blood = new Animation();
            this.isAttacking = false;
        }

        #endregion

        #region Public Events

        /// <summary>
        /// The movement event.
        /// </summary>
        public event DelegateMovement.Movement MovementEvent;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets a value indicating whether Active.
        /// </summary>
        public bool Active
        {
            get
            {
                return this.active;
            }

            set
            {
                Contract.Ensures(this.Active == value);
                this.active = value;
            }
        }

        /// <summary>
        /// Gets ActiveWeapon.
        /// </summary>
        public Weapon ActiveWeapon
        {
            get
            {
                Contract.Requires(this.Weapons.Count > 0);
                return this.Weapons[0];
            }
        }

        /// <summary>
        /// Gets Animation.
        /// </summary>
        public Animation Animation { get; private set; }

        /// <summary>
        /// Gets or sets blood.
        /// </summary>
        public Animation Blood { get; set; }

        /// <summary>
        /// Gets or sets the direction.
        /// </summary>
        public MovementsEnum Direction { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether true if the figure of the player is moving on the screen
        /// </summary>
        public bool IsGraphicallyMoving { get; set; }

        /// <summary>
        /// Gets or sets Life.
        /// </summary>
        public uint Life
        {
            get
            {
                Contract.Ensures(Contract.Result<uint>() >= 0);
                return this.life;
            }

            set
            {
                Contract.Requires(value >= 0 && value <= this.MaximumLife);
                Contract.Ensures(this.Life >= 0);

                // If the player has been attacked show some blood.
                if (this.life > value)
                {
                    this.Blood.Active = true;
                }

                this.life = value;
                if (this.life == 0)
                {
                    this.Animation.Active = false;
                }
            }
        }

        /// <summary>
        /// Gets MaximunLife.
        /// </summary>
        public uint MaximumLife
        {
            get
            {
                Contract.Ensures(Contract.Result<uint>() > 0);
                return this.maximumLife;
            }

            private set
            {
                Contract.Requires(value > 0);
                this.maximumLife = value;
            }
        }

        /// <summary>
        /// Gets or sets Money.
        /// </summary>
        public uint Money
        {
            get
            {
                Contract.Ensures(Contract.Result<uint>() >= 0);
                return this.money;
            }

            set
            {
                Contract.Ensures(this.money == value);
                this.money = value;
            }
        }

        /// <summary>
        /// Gets Name.
        /// </summary>
        public string Name
        {
            get
            {
                Contract.Ensures(Contract.Result<string>() != null);
                return this.name;
            }

            private set
            {
                this.name = value;
            }
        }

        /// <summary>
        /// Gets or sets NumberFloor.
        /// </summary>
        public int NumberFloor
        {
            get
            {
                return this.numberFloor;
            }

            set
            {
                this.numberFloor = value;
                this.Weapons.ForEach(weapon => weapon.NumberFloor = this.NumberFloor);
            }
        }

        /// <summary>
        /// Gets or sets Position.
        /// </summary>
        public Vector2 Position
        {
            get
            {
                return this.position;
            }

            set
            {
                Contract.Ensures(this.position == value);
                this.position = value;
            }
        }

        /// <summary>
        /// Gets RealPosition.
        /// </summary>
        public Vector2 RealPosition { get; private set; }

        /// <summary>
        /// Gets or sets Score.
        /// </summary>
        public uint Score
        {
            get
            {
                Contract.Ensures(Contract.Result<uint>() >= 0);
                return this.score;
            }

            set
            {
                Contract.Ensures(this.score == value);
                this.score = value;
            }
        }

        /// <summary>
        /// Gets Weapons.
        /// </summary>
        public List<Weapon> Weapons
        {
            get
            {
                Contract.Ensures(Contract.Result<List<Weapon>>() != null);
                return this.weapons;
            }

            private set
            {
                this.weapons = value;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets activeFloorWidth.
        /// </summary>
        private float ActiveFloorWidth
        {
            get
            {
                return Client.Floors.Find(f => f.Number == this.numberFloor).Width;
            }
        }

        /// <summary>
        /// Gets Height.
        /// </summary>
        private int Height
        {
            get
            {
                return this.Animation.FrameHeight;
            }
        }

        /// <summary>
        /// Gets Width.
        /// </summary>
        private int Width
        {
            get
            {
                return this.Animation.FrameWidth;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
            this.Weapons.ForEach(weapon => weapon.Dispose());
            this.Animation.Dispose();
            this.Blood.Dispose();
        }

        /// <summary>
        /// The draw.
        /// </summary>
        /// <param name="spriteBatch">
        /// The sprite Batch.
        /// </param>
        public void Draw(SpriteBatch spriteBatch)
        {
            this.Animation.Draw(spriteBatch);
            this.Blood.Draw(spriteBatch);
        }

        /// <summary>
        /// The load content.
        /// </summary>
        public void LoadContent()
        {
            this.Texture = Client.Content.Load<Texture2D>(this.Image);
            this.Animation.Initialize(this.Texture, Vector2.Zero, (int)this.numberFrames, Color.White, 1f, true, false);

            var bloodTexture = Client.Content.Load<Texture2D>("blood");
            this.Blood.Initialize(bloodTexture, Vector2.Zero, 23, Color.White, 1f, false, true);
            this.Blood.Active = false;

            this.ChangeWeaponSound = Client.Content.Load<SoundEffect>(ChangeWeaponName);
        }

        /// <summary>
        /// The update.
        /// </summary>
        /// <param name="gameTime">
        /// The game Time.
        /// </param>
        /// <param name="floorPosition">
        /// The floor Position.
        /// </param>
        public void Update(GameTime gameTime, Vector2 floorPosition)
        {
            this.InputState.Update();
            this.RealPosition = new Vector2(Math.Abs(floorPosition.X) + this.Position.X, this.Position.Y);
            this.Move(floorPosition, this.ActiveFloorWidth);
            this.TakeElevator();
            if (this.Weapons.Count > 0)
            {
                this.ChangeWeapon();
            }

            this.Attack(gameTime);
            this.Animation.Position = this.Position;
            this.Blood.Position = this.Position;
            this.Animation.Update(gameTime);
            this.Blood.Update(gameTime);
            this.Weapons.ForEach(weapon => weapon.Update(gameTime, floorPosition));
            this.CheckStatus();
        }

        #endregion

        #region Methods

        /// <summary>
        /// The attack.
        /// </summary>
        /// <param name="gameTime">
        /// The game Time.
        /// </param>
        private void Attack(GameTime gameTime)
        {
            KeyboardState currentKeyboardState = Keyboard.GetState();

            if (!currentKeyboardState.IsKeyDown(Keys.Space))
            {
                this.isAttacking = false;
                return;
            }

            if (this.isAttacking)
            {
                return;
            }

            this.isAttacking = true;

            if (this.ActiveWeapon == null)
            {
                return;
            }

            this.ActiveWeapon.Use(
                gameTime, 
                new Vector2(this.RealPosition.X, this.RealPosition.Y + (this.Animation.FrameHeight / 2)), 
                this.Direction);
        }

        /// <summary>
        /// The change weapon.
        /// </summary>
        private void ChangeWeapon()
        {
            Contract.Requires(this.Weapons.Count > 0);
            var inputAction = new InputAction(new[] { Keys.Q }, true);
            if (!inputAction.Evaluate(this.InputState))
            {
                return;
            }

            this.ChangeWeaponSound.Play();

            // Put the activeWeapon at the end of the list.
            this.Weapons.Add(this.Weapons[0]);
            this.Weapons.RemoveAt(0);
        }

        /// <summary>
        /// The check status.
        /// </summary>
        private void CheckStatus()
        {
            if (this.life <= 0)
            {
                Client.GameState = GameStateEnum.GameOver;
            }
            else if (
                Client.Floors.TrueForAll(
                    floor =>
                    floor.Enemies.TrueForAll(enemy => enemy.Life <= 0)
                    && floor.MaxNumberOfEnemies == floor.NumberOfSpawnedEnemies))
            {
                Client.GameState = GameStateEnum.Win;
            }
        }

        /// <summary>
        /// The move.
        /// </summary>
        /// <param name="floorPosition">
        /// The floor Position.
        /// </param>
        /// <param name="floorWidth">
        /// The width of the floor.
        /// </param>
        /// Author: Mikkel Thordal
        private void Move(Vector2 floorPosition, float floorWidth)
        {
            if (!Client.Elevator.Travelling)
            {
                var inputActionLeft = new InputAction(new[] { Keys.Left }, false);
                if (inputActionLeft.Evaluate(this.InputState))
                {
                    this.Animation.IsMoving = true;
                    if (this.Position.X > Client.GraphicsDevice.Viewport.Width * NoMovementsScale
                        || floorPosition.X.Equals(0))
                    {
                        if (this.Direction == MovementsEnum.Right)
                        {
                            this.Animation.Flip(MovementsEnum.Left);
                        }

                        this.Position = new Vector2(this.Position.X - Speed, this.Position.Y);
                        this.IsGraphicallyMoving = true;
                    }
                    else
                    {
                        this.IsGraphicallyMoving = false;
                    }

                    this.MovementEvent(MovementsEnum.Left);
                    this.Direction = MovementsEnum.Left;
                }

                var inputActionRight = new InputAction(new[] { Keys.Right }, false);
                if (inputActionRight.Evaluate(this.InputState))
                {
                    this.Animation.IsMoving = true;
                    if (this.Position.X < Client.GraphicsDevice.Viewport.Width * (0.8 - NoMovementsScale)
                        || Math.Abs(floorPosition.X - Client.GraphicsDevice.Viewport.Width).Equals(floorWidth))
                    {
                        if (this.Direction == MovementsEnum.Left)
                        {
                            this.Animation.Flip(MovementsEnum.Right);
                        }

                        this.Position = new Vector2(this.Position.X + Speed, this.Position.Y);
                        this.IsGraphicallyMoving = true;
                    }
                    else
                    {
                        this.IsGraphicallyMoving = false;
                    }

                    this.MovementEvent(MovementsEnum.Right);
                    this.Direction = MovementsEnum.Right;
                }

                if (!inputActionLeft.Evaluate(this.InputState) && !inputActionRight.Evaluate(this.InputState))
                {
                    this.Animation.IsMoving = false;
                }

                var inputAction = new InputAction(new[] { Keys.Up }, false);
                if (this.jumping)
                {
                    this.Position = new Vector2(this.Position.X, this.Position.Y + this.jumpSpeed);
                    this.jumpSpeed += 2;
                    this.Animation.IsMoving = false;
                    if (this.Position.Y >= this.startY)
                    {
                        this.Position = new Vector2(this.Position.X, this.startY);
                        this.jumping = false;
                    }
                }
                else if (inputAction.Evaluate(this.InputState))
                {
                    this.MovementEvent(MovementsEnum.Up);
                    this.startY = this.Position.Y;
                    this.jumping = true;
                    this.jumpSpeed = -38;
                }

                inputAction = new InputAction(new[] { Keys.Down }, false);
                if (inputAction.Evaluate(this.InputState))
                {
                    this.MovementEvent(MovementsEnum.Down);
                }
            }

            this.Position =
                new Vector2(
                    MathHelper.Clamp(this.Position.X, 0, Client.GraphicsDevice.Viewport.Width - this.Width), 
                    MathHelper.Clamp(this.Position.Y, 0, Client.GraphicsDevice.Viewport.Height - this.Height));
        }

        /// <summary>
        /// The take elevator.
        /// </summary>
        /// Author: Mikkel Thordal
        private void TakeElevator()
        {
            if (this.numberFloor == Client.Elevator.NumberFloor && this.position.X > Client.Elevator.Position.X)
            {
                var inputAction = new InputAction(new[] { Keys.D0 }, true);
                if (inputAction.Evaluate(this.InputState))
                {
                    Client.Elevator.GoToFloor(0, this);
                    return;
                }

                inputAction = new InputAction(new[] { Keys.D1 }, true);
                if (inputAction.Evaluate(this.InputState))
                {
                    Client.Elevator.GoToFloor(1, this);
                    return;
                }

                inputAction = new InputAction(new[] { Keys.D2 }, true);
                if (inputAction.Evaluate(this.InputState))
                {
                    Client.Elevator.GoToFloor(2, this);
                    return;
                }

                inputAction = new InputAction(new[] { Keys.D3 }, true);
                if (inputAction.Evaluate(this.InputState))
                {
                    Client.Elevator.GoToFloor(3, this);
                    return;
                }

                inputAction = new InputAction(new[] { Keys.D4 }, true);
                if (inputAction.Evaluate(this.InputState))
                {
                    Client.Elevator.GoToFloor(4, this);
                    return;
                }

                inputAction = new InputAction(new[] { Keys.D5 }, true);
                if (inputAction.Evaluate(this.InputState))
                {
                    if (
                        Client.Floors.Where(f => f.Number < 5).ToList().TrueForAll(
                            f => f.Enemies.Count == 0 && f.NumberOfSpawnedEnemies == f.MaxNumberOfEnemies))
                    {
                        Client.Elevator.GoToFloor(5, this);
                        return;
                    }
                }
            }
        }

        #endregion
    }
}